﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Header.cs" company="Rolosoft Ltd">
//   © Rolosoft Ltd
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

#region License

// Copyright 2013 Rolosoft Ltd
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace Rsft.Net.Dns.Entities
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Net;

    using Rsft.Net.Dns.Interfaces;

    #endregion

    /// <summary>
    ///     The header.
    /// </summary>
    public sealed class Header
    {
        #region Fields

        /// <summary>
        ///     The flags.
        /// </summary>
        private ushort flags;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Header"/> class.
        /// </summary>
        /// <param name="recordReader">
        /// The record reader.
        /// </param>
        public Header(IRecordReader recordReader)
        {
            Contract.Requires(recordReader != null);

            this.Id = recordReader.ReadUInt16();
            this.flags = recordReader.ReadUInt16();
            this.QdCount = recordReader.ReadUInt16();
            this.Ancount = recordReader.ReadUInt16();
            this.Nscount = recordReader.ReadUInt16();
            this.Arcount = recordReader.ReadUInt16();
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Header" /> class.
        /// </summary>
        public Header()
        {
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets or sets the number of resource records in the answer section
        /// </summary>
        /// <value>The ancount.</value>
        public ushort Ancount { get; set; }

        /// <summary>
        ///     Gets or sets the number of resource records in the additional records section
        /// </summary>
        /// <value>The arcount.</value>
        public ushort Arcount { get; set; }

        /// <summary>
        ///     Gets or sets a value indicating whether [authoritive answer].
        /// </summary>
        /// <value>
        ///     <c>true</c> if [authoritive answer]; otherwise, <c>false</c>.
        /// </value>
        public bool AuthoritiveAnswer
        {
            get
            {
                return GetBits(this.flags, 10, 1) == 1;
            }

            set
            {
                this.flags = SetBits(this.flags, 10, 1, value);
            }
        }

        /// <summary>
        ///     Gets the header as a byte array
        /// </summary>
        public IEnumerable<byte> Data
        {
            get
            {
                var data = new List<byte>();
                data.AddRange(WriteShort(this.Id));
                data.AddRange(WriteShort(this.flags));
                data.AddRange(WriteShort(this.QdCount));
                data.AddRange(WriteShort(this.Ancount));
                data.AddRange(WriteShort(this.Nscount));
                data.AddRange(WriteShort(this.Arcount));
                return data.ToArray();
            }
        }

        /// <summary>
        ///     Gets or sets An identifier assigned by the program
        /// </summary>
        /// <value>The id.</value>
        public ushort Id { get; set; }

        /// <summary>
        ///     Gets or sets the number of name server resource records in the authority records section
        /// </summary>
        /// <value>The nscount.</value>
        public ushort Nscount { get; set; }

        /// <summary>
        ///     Gets or sets the opcode type.
        /// </summary>
        /// <value>The opcode type.</value>
        public OPCodeType OpCodeType
        {
            get
            {
                return (OPCodeType)GetBits(this.flags, 11, 4);
            }

            set
            {
                this.flags = SetBits(this.flags, 11, 4, (ushort)value);
            }
        }

        /// <summary>
        ///     Gets or sets the number of entries in the question section
        /// </summary>
        /// <value>The QD count.</value>
        public ushort QdCount { get; set; }

        /// <summary>
        ///     Gets or sets a value indicating whether query (false), or a response (true)
        /// </summary>
        /// <value>
        ///     <c>true</c> if QR; otherwise, <c>false</c>.
        /// </value>
        public bool QueryOrResponse
        {
            get
            {
                return GetBits(this.flags, 15, 1) == 1;
            }

            set
            {
                this.flags = SetBits(this.flags, 15, 1, value);
            }
        }

        /// <summary>
        ///     Gets or sets a value indicating whether [recursion available].
        /// </summary>
        /// <value>
        ///     <c>true</c> if [recursion available]; otherwise, <c>false</c>.
        /// </value>
        public bool RecursionAvailable
        {
            get
            {
                return GetBits(this.flags, 7, 1) == 1;
            }

            set
            {
                this.flags = SetBits(this.flags, 7, 1, value);
            }
        }

        /// <summary>
        ///     Gets or sets a value indicating whether [recursion desired].
        /// </summary>
        /// <value>
        ///     <c>true</c> if [recursion desired]; otherwise, <c>false</c>.
        /// </value>
        public bool RecursionDesired
        {
            get
            {
                return GetBits(this.flags, 8, 1) == 1;
            }

            set
            {
                this.flags = SetBits(this.flags, 8, 1, value);
            }
        }

        /// <summary>
        ///     Gets or sets the response code.
        /// </summary>
        /// <value>The response code.</value>
        public RCodeType ResponseCode
        {
            get
            {
                return (RCodeType)GetBits(this.flags, 0, 4);
            }

            set
            {
                this.flags = SetBits(this.flags, 0, 4, (ushort)value);
            }
        }

        /// <summary>
        ///     Gets or sets a value indicating whether this <see cref="Header" /> is truncation.
        /// </summary>
        /// <value>
        ///     <c>true</c> if truncation; otherwise, <c>false</c>.
        /// </value>
        public bool Truncation
        {
            get
            {
                return GetBits(this.flags, 9, 1) == 1;
            }

            set
            {
                this.flags = SetBits(this.flags, 9, 1, value);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the bits.
        /// </summary>
        /// <param name="oldValue">
        /// The old value.
        /// </param>
        /// <param name="position">
        /// The position.
        /// </param>
        /// <param name="length">
        /// The length.
        /// </param>
        /// <returns>
        /// Gotten bits.
        /// </returns>
        private static ushort GetBits(ushort oldValue, int position, int length)
        {
            // sanity check
            if (length <= 0 || position >= 16)
            {
                return 0;
            }

            // get some mask to put on
            var mask = (2 << (length - 1)) - 1;

            return (ushort)((oldValue >> position) & mask);
        }

        /// <summary>
        /// Sets the bits.
        /// </summary>
        /// <param name="oldValue">
        /// The old value.
        /// </param>
        /// <param name="position">
        /// The position.
        /// </param>
        /// <param name="length">
        /// The length.
        /// </param>
        /// <param name="blnValue">
        /// if set to <c>true</c> [BLN value].
        /// </param>
        /// <returns>
        /// The set bits
        /// </returns>
        private static ushort SetBits(ushort oldValue, int position, int length, bool blnValue)
        {
            return SetBits(oldValue, position, length, blnValue ? (ushort)1 : (ushort)0);
        }

        /// <summary>
        /// Sets the bits.
        /// </summary>
        /// <param name="oldValue">
        /// The old value.
        /// </param>
        /// <param name="position">
        /// The position.
        /// </param>
        /// <param name="length">
        /// The length.
        /// </param>
        /// <param name="newValue">
        /// The new value.
        /// </param>
        /// <returns>
        /// The set bits
        /// </returns>
        private static ushort SetBits(ushort oldValue, int position, int length, ushort newValue)
        {
            // sanity check
            if (length <= 0 || position >= 16)
            {
                return oldValue;
            }

            var mask = (2 << (length - 1)) - 1;

            oldValue &= (ushort)~(mask << position);

            oldValue |= (ushort)((newValue & mask) << position);
            return oldValue;
        }

        /// <summary>
        /// Writes the short.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The gotten bytes
        /// </returns>
        private static IEnumerable<byte> WriteShort(ushort value)
        {
            return BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)value));
        }

        #endregion
    }
}